package com.gc.materialdesign.views;

import com.gc.materialdesign.ResourceTable;
import com.gc.materialdesign.utils.ResUtil;
import com.gc.materialdesign.utils.ShapeUtil;
import com.gc.materialdesign.utils.Utils;
import com.gc.materialdesign.utils.ViewHelper;

import ohos.agp.components.*;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.TextAlignment;
import ohos.agp.window.dialog.CommonDialog;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

public class Slider extends CustomView implements Component.TouchEventListener {
    private String backgroundColor = "#4CAF50FF";
    private Ball ball;
    private PixelMap bitmap;
    private int max = 100;
    private int min = 0;
    private NumberIndicator numberIndicator;
    private OnValueChangedListener onValueChangedListener;
    private boolean placedBall = false;
    private boolean press = false;
    private boolean showNumberIndicator = false;
    private int value = 0;
    private Context context;

    public Slider(Context context) {
        this(context, null);
    }

    public Slider(Context context, AttrSet attrs) {
        this(context, attrs, null);
    }

    public Slider(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        setAttributes(attrs);
        setTouchEventListener(this);
    }

    public int getMax() {
        return max;
    }

    public void setMax(int max) {
        this.max = max;
    }

    public int getMin() {
        return min;
    }

    public void setMin(int min) {
        this.min = min;
    }

    public OnValueChangedListener getOnValueChangedListener() {
        return onValueChangedListener;
    }

    public void setOnValueChangedListener(OnValueChangedListener onValueChangedListener) {
        this.onValueChangedListener = onValueChangedListener;
    }

    // GETERS & SETTERS

    public int getValue() {
        return value;
    }

    public void setValue(final int value) {
        if (!placedBall) {
            setLayoutRefreshedListener(
                    new LayoutRefreshedListener() {
                        @Override
                        public void onRefreshed(Component component) {
                            setValue(value);
                        }
                    });
        } else {
            this.value = value;
            float division = (ball.xFin - ball.xIni) / max;
            ViewHelper.setX(ball, value * division + getHeight() / 2 - ball.getWidth() / 2);
            ball.changeBackground();
        }
    }

    @Override
    public void invalidate() {
        ball.invalidate();
        super.invalidate();
    }

    public boolean isShowNumberIndicator() {
        return showNumberIndicator;
    }

    public void setShowNumberIndicator(boolean showNumberIndicator) {
        this.showNumberIndicator = showNumberIndicator;
        //        numberIndicator = showNumberIndicator ? new NumberIndicator(getContext()) : null;
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        isLastTouch = true;
        if (isEnabled()) {
            MmiPoint mmiPoint = event.getPointerScreenPosition(event.getIndex());
            if (event.getAction() == TouchEvent.PRIMARY_POINT_DOWN || event.getAction() == TouchEvent.POINT_MOVE) {
                if (numberIndicator != null && !numberIndicator.isShowing()) {
                    numberIndicator.show();
                }
                if (Math.abs(mmiPoint.getX() - getLocationOnScreen()[0]) <= getWidth()) {
                    press = true;
                    // calculate value
                    int newValue;
                    float division = (ball.xFin - ball.xIni) / (max - min);
                    if (Math.abs(mmiPoint.getX() - getLocationOnScreen()[0]) > ball.xFin) {
                        newValue = max;
                    } else if (Math.abs(mmiPoint.getX() - getLocationOnScreen()[0]) < ball.xIni) {
                        newValue = min;
                    } else {
                        newValue =
                                min
                                        + (int)
                                                ((Math.abs(mmiPoint.getX() - getLocationOnScreen()[0]) - ball.xIni)
                                                        / division);
                    }
                    if (value != newValue) {
                        value = newValue;
                        if (onValueChangedListener != null) {
                            onValueChangedListener.onValueChanged(newValue);
                        }
                    }
                    // move ball indicator
                    float x = Math.abs(mmiPoint.getX() - getLocationOnScreen()[0]);
                    x = Math.max(x, ball.xIni);
                    x = Math.min(x, ball.xFin);
                    ViewHelper.setX(ball, x);
                    ball.changeBackground();

                    // If slider has number indicator
                    if (numberIndicator != null) {
                        // move number indicator
                        numberIndicator.indicator.x = x;
                        numberIndicator.indicator.finalY = Utils.getRelativeTop(this) - getHeight() / 2;
                        numberIndicator.indicator.finalSize = getHeight() / 2;
                        numberIndicator.text.setText("");
                    }

                } else {
                    press = false;
                    isLastTouch = false;
                    if (numberIndicator != null) {
                        numberIndicator.hide();
                    }
                }

            } else if (event.getAction() == TouchEvent.PRIMARY_POINT_UP || event.getAction() == TouchEvent.CANCEL) {
                if (numberIndicator != null) {
                    numberIndicator.hide();
                }
                isLastTouch = false;
                press = false;
            }
        }
        return true;
    }

    public void setBackgroundColor(String color) {
        backgroundColor = color;
        if (isEnabled()) {
            beforeBackground = backgroundColor;
        }
    }

    /**
     * Make a dark color to press effect
     *
     * @return 改变后的颜色值
     */
    protected String makePressColorString() {
        int colorInt;
        if (backgroundColor.length() == 7) {
            colorInt = Color.getIntColor(backgroundColor);
        } else {
            colorInt = Color.getIntColor(backgroundColor.substring(0, backgroundColor.length() - 2));
        }
        int r = (colorInt >> 16) & 0xFF;
        int g = (colorInt >> 8) & 0xFF;
        int b = (colorInt >> 0) & 0xFF;
        r = Math.max(r - 30, 0);
        g = Math.max(g - 30, 0);
        b = Math.max(b - 30, 0);
        StringBuilder builder =
                new StringBuilder("#")
                        .append(Integer.toHexString(30))
                        .append(colorPrefix(Integer.toHexString(r)))
                        .append(colorPrefix(Integer.toHexString(g)))
                        .append(colorPrefix(Integer.toHexString(b)));
        return builder.toString();
    }

    private String colorPrefix(String string) {
        return string.length() > 1 ? string : "00";
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        super.onDraw(component, canvas);
        if (!placedBall) {
            placeBall();
        }

        Paint paint = new Paint();

        if (value == min) {
            // Crop line to transparent effect
            if (bitmap == null) {
                PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
                initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
                initializationOptions.size = new Size(getWidth(), getHeight());
                bitmap = PixelMap.create(initializationOptions);
            }
            Texture texture = new Texture(bitmap);
            Canvas temp = new Canvas(texture);
            paint.setColor(new Color(Color.getIntColor("#B0B0B0")));
            paint.setStrokeWidth(Utils.dpToPx(2, context));
            temp.drawLine(
                    new Point(getHeight() / 2, getHeight() / 2),
                    new Point(getWidth() - getHeight() / 2, getHeight() / 2),
                    paint);
            Paint transparentPaint = new Paint();
            transparentPaint.setColor(new Color(ResUtil.getColor(context, ResourceTable.Color_transparent)));
            transparentPaint.setBlendMode(BlendMode.CLEAR);
            temp.drawCircle(
                    ViewHelper.getX(ball) + ball.getWidth() / 2,
                    ViewHelper.getY(ball) + ball.getHeight() / 2,
                    ball.getWidth() / 2,
                    transparentPaint);

            canvas.drawPixelMapHolder(new PixelMapHolder(bitmap), 0, 0, new Paint());
        } else {
            paint.setColor(new Color(Color.getIntColor("#B0B0B0")));
            paint.setStrokeWidth(Utils.dpToPx(2, context));
            canvas.drawLine(
                    new Point(getHeight() / 2, getHeight() / 2),
                    new Point(getWidth() - getHeight() / 2, getHeight() / 2),
                    paint);
            paint.setColor(new Color(Color.getIntColor(Utils.argb(backgroundColor))));
            float division = (ball.xFin - ball.xIni) / (max - min);
            int value = this.value - min;

            canvas.drawLine(
                    new Point(getHeight() / 2, getHeight() / 2),
                    new Point(value * division + getHeight() / 4 * 3, getHeight() / 2),
                    paint);
        }

        if (press && !showNumberIndicator) {
            paint.setColor(new Color(Color.getIntColor(Utils.argb(backgroundColor))));
            paint.setAntiAlias(true);
            canvas.drawCircle(ViewHelper.getX(ball) + ball.getWidth() / 2, getHeight() / 2, getHeight() / 3, paint);
        }
        getContext().getUITaskDispatcher().asyncDispatch(this::invalidate);
    }

    // Set atributtes of XML to View
    protected void setAttributes(AttrSet attrs) {
        // Set size of view
        setMinHeight(Utils.dpToPx(48, context));
        setMinWidth(Utils.dpToPx(80, context));

        // Set background Color
        // Color by resource
        boolean backgroundIsPresent = attrs.getAttr("background").isPresent();
        if (backgroundIsPresent) {
            String color = attrs.getAttr("background").get().getStringValue();
            setBackgroundColor(color);
        }

        boolean showNumberIndicatorIsPresent = attrs.getAttr("showNumberIndicator").isPresent();
        if (showNumberIndicatorIsPresent) {
            showNumberIndicator = attrs.getAttr("showNumberIndicator").get().getBoolValue();
        }

        boolean minIsPresent = attrs.getAttr("min").isPresent();
        if (minIsPresent) {
            min = attrs.getAttr("min").get().getIntegerValue();
        }

        boolean maxIsPresent = attrs.getAttr("max").isPresent();
        if (maxIsPresent) {
            max = attrs.getAttr("max").get().getIntegerValue();
        }

        boolean valueIsPresent = attrs.getAttr("value").isPresent();
        if (valueIsPresent) {
            value = attrs.getAttr("value").get().getIntegerValue();
        }

        ball = new Ball(context);
        LayoutConfig layoutConfig =
                new DependentLayout.LayoutConfig(Utils.dpToPx(20, context), Utils.dpToPx(20, context));
        layoutConfig.addRule(LayoutConfig.VERTICAL_CENTER, LayoutConfig.TRUE);
        ball.setLayoutConfig(layoutConfig);
        addComponent(ball);

        // Set if slider content number indicator
        if (showNumberIndicator) {
            numberIndicator = new NumberIndicator();
        }
    }

    private void placeBall() {
        ViewHelper.setX(ball, getHeight() / 2 - ball.getWidth() / 2);
        ball.xIni = ViewHelper.getX(ball);
        ball.xFin = getWidth() - getHeight() / 2 - ball.getWidth() / 2;
        ball.xCen = getWidth() / 2 - ball.getWidth() / 2;
        placedBall = true;
    }

    // Event when slider change value
    public interface OnValueChangedListener {
        void onValueChanged(int value);
    }

    class Ball extends Component {
        float xIni, xFin, xCen;

        public Ball(Context context) {
            super(context);
            Element element = new ShapeElement(context, ResourceTable.Graphic_background_switch_ball_uncheck);
            setBackground(element);
        }

        public void changeBackground() {
            if (value != min) {
                ShapeElement element = ShapeUtil.createCircleDrawable(Color.getIntColor(Utils.rgba(backgroundColor)));
                setBackground(element);
            } else {
                Element element = new ShapeElement(context, ResourceTable.Graphic_background_switch_ball_uncheck);
                setBackground(element);
            }
        }
    }

    // Slider Number Indicator

    class Indicator extends DependentLayout implements DrawTask {
        boolean animate = true;
        // Final size after animation
        float finalSize = 0;
        // Final y position after animation
        float finalY = 0;
        boolean numberIndicatorResize = false;
        // Size of number indicator
        float size = 0;
        // Position of number indicator
        float x = 0;
        float y = 0;

        public Indicator(Context context) {
            super(context);
            addDrawTask(this);
        }

        @Override
        public void onDraw(Component component, Canvas canvas) {
            if (!numberIndicatorResize) {
                LayoutConfig params = (LayoutConfig) numberIndicator.text.getLayoutConfig();
                params.height = (int) finalSize * 2;
                params.width = (int) finalSize * 2;
                numberIndicator.text.setLayoutConfig(params);
            }

            Paint paint = new Paint();
            paint.setAntiAlias(true);
            paint.setColor(new Color(Color.getIntColor(Utils.argb(backgroundColor))));
            if (animate) {
                if (y == 0) {
                    y = finalY + finalSize * 2;
                }
                y -= Utils.dpToPx(6, context);
                size += Utils.dpToPx(2, context);
            }
            canvas.drawCircle(
                    ViewHelper.getX(ball)
                            + Utils.getRelativeLeft((Component) ball.getComponentParent())
                            + ball.getWidth() / 2,
                    y,
                    size,
                    paint);
            if (animate && size >= finalSize) {
                animate = false;
            }
            if (!animate) {
                ViewHelper.setX(
                        numberIndicator.text,
                        (ViewHelper.getX(ball)
                                        + Utils.getRelativeLeft((Component) ball.getComponentParent())
                                        + ball.getWidth() / 2)
                                - size);
                ViewHelper.setY(numberIndicator.text, y - size);
                numberIndicator.text.setText(value + "");
            }

            getContext().getUITaskDispatcher().asyncDispatch(this::invalidate);
        }
    }

    class NumberIndicator {
        Indicator indicator;
        Text text;
        CommonDialog dialog;

        public NumberIndicator() {
            init();
        }

        private void init() {
            dialog = new CommonDialog(context);
            dialog.setAutoClosable(false);
            dialog.setTransparent(true);

            DependentLayout dependentLayout = new DependentLayout(getContext());
            LayoutConfig layoutConfig = new LayoutConfig(LayoutConfig.MATCH_CONTENT, LayoutConfig.MATCH_CONTENT);
            dependentLayout.setLayoutConfig(layoutConfig);
            dependentLayout.setBackground(
                    ShapeUtil.createCircleDrawable(Color.getIntColor(Utils.rgba(backgroundColor))));

            indicator = new Indicator(getContext());
            indicator.setLayoutConfig(
                    new DependentLayout.LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT));
            dependentLayout.addComponent(indicator);

            text = new Text(getContext());
            text.setTextColor(Color.WHITE);
            text.setTextAlignment(TextAlignment.CENTER);
            dependentLayout.addComponent(text);

            dialog.setContentCustomComponent(dependentLayout);
        }

        public boolean isShowing() {
            return dialog.isShowing();
        }

        public void show() {
            dialog.show();
        }

        public void hide() {
            dialog.hide();
            indicator.y = 0;
            indicator.size = 0;
            indicator.animate = true;
        }
    }
}
